﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using Sonce.Editor.Tools;

namespace Sonce.Editor
{
	/// <summary>
	/// Class that represents vector graphics editor as whole.
	/// </summary>
	public partial class Editor : UserControl
	{
		#region Members
		private ITool selectedTool = null;
		private ITool alternativeTool = null; //Alternative tool: when modifier keys are pressed, usually pan and zoom
		private bool isHoldingNode = false;
		private bool isMousePressed = false;
		private bool isDrawingPolyline = false;
		private bool snapToGrid = true;
		private double snapRange = 10;
		private double zoom = 1.0;
		private double angle = 0.0;
		private string xamlElement = null;
		private Point startPoint;
		private Point endPoint;
		private Point movePoint;
		private UndoManager undoManager;
		private LayoutManager layoutManager;
		private LogicManager logicManager;
		private SelectionManager selectionManager;
		#endregion

		public int CurrentDocumentId = -1; //Reserved for future use

		#region Properties
		/// <summary>
		/// Get or set the selected tool.
		/// </summary>
		public ITool SelectedTool
		{
			get
			{
				return this.selectedTool;
			}
			set
			{
				if (value != null)
				{
					ReleaseTool();
					this.selectedTool = value;
					this.selectedTool.Editor = this;
				}
			}
		}

		/// <summary>
		/// Enable snapping to the nearest snap point.
		/// </summary>
		public bool SnapToGrid
		{
			get { return this.snapToGrid; }
			set { this.snapToGrid = value; }
		}

		/// <summary>
		/// Get or set the snap range in pixels.
		/// </summary>
		public double SnapRange
		{
			get
			{
				return this.snapRange;
			}
			set
			{
				if (value > 0.0)
				{
					this.snapRange = value;
				}
			}
		}

		/// <summary>
		/// Toggle grid visibility.
		/// </summary>
		public bool ShowGrid
		{
			get
			{
				return (layerGrid.Visibility == Visibility.Visible);
			}
			set
			{
				layerGrid.Visibility = (value ? Visibility.Visible : Visibility.Collapsed);
			}
		}

		/// <summary>
		/// Get or set the zoom factor. Value 1.0 means original size, lower values to zoom out, higher values to zoom in.
		/// </summary>
		public double Zoom
		{
			get
			{
				return this.zoom;
			}
			set
			{
				this.zoom = value;

				ScaleTransform scaleTransform = new ScaleTransform();
				scaleTransform.ScaleX = value;
				scaleTransform.ScaleY = value;
				this.RenderTransform = scaleTransform;
			}
		}

		/// <summary>
		/// Get or set the angle that will be applied to every new element drawn with the Library tool.
		/// </summary>
		public double Angle
		{
			get
			{
				return this.angle;
			}
			set
			{
				this.angle = value;
			}
		}

		/// <summary>
		/// Get or set the editor width. Layers are NOT resized.
		/// </summary>
		public new double Width
		{
			get
			{
				return base.Width;
			}
			set
			{
				base.Width = value;
				LayoutRoot.Width = value;
			}
		}

		/// <summary>
		/// Get or set the editor height. Layers are NOT resized.
		/// </summary>
		public new double Height
		{
			get
			{
				return base.Height;
			}
			set
			{
				base.Height = value;
				LayoutRoot.Height = value;
			}
		}

		/// <summary>
		/// Get or set the xaml of an visual object that is drawn with the Library tool.
		/// </summary>
		public string XamlElement
		{
			get
			{
				return this.xamlElement;
			}
			set
			{
				this.xamlElement = value;
			}
		}

		/// <summary>
		/// Get a list of selected visual objects.
		/// </summary>
		public List<FrameworkElement> SelectedElements
		{
			get
			{
				return this.SelectionManager.SelectedElements;
			}
		}
		#endregion

		#region Layers
		/// <summary>
		/// Gets a layer that represents a working area where vector image is drawn.
		/// </summary>
		public Layer LayerWorking
		{
			get
			{
				return this.layerWorking;
			}
		}

		/// <summary>
		/// Gets a layer that represents partial or full element preview while drawing element.
		/// </summary>
		internal Layer LayerPreview
		{
			get
			{
				return this.layerPreview;
			}
		}

		/// <summary>
		/// Gets a layer that represents selected and highlighted elements (permanently).
		/// </summary>
		internal Layer LayerSelect
		{
			get
			{
				return this.layerSelect;
			}
		}

		/// <summary>
		/// Gets a layer that represents highlighted elements on mouse over (temporary).
		/// </summary>
		internal Layer LayerHighlight
		{
			get
			{
				return this.layerHighlight;
			}
		}

		/// <summary>
		/// Gets a layer that represents nodes of elements.
		/// </summary>
		internal Layer LayerNodes
		{
			get
			{
				return this.layerNodes;
			}
		}

		/// <summary>
		/// Gets a layer that represents rectangle while selecting elements.
		/// </summary>
		internal Layer LayerFloat
		{
			get
			{
				return this.layerFloat;
			}
		}

		/// <summary>
		/// Gets a layer that represents cursor area.
		/// </summary>
		internal Layer LayerCursor
		{
			get
			{
				return this.layerCursor;
			}
		}
		#endregion

		#region Properties
		internal Point StartPoint
		{
			get
			{
				return this.startPoint;
			}
		}

		internal Point MovePoint
		{
			get
			{
				return this.movePoint;
			}
		}

		internal Point EndPoint
		{
			get
			{
				return this.endPoint;
			}
		}

		public LayoutManager LayoutManager
		{
			get
			{
				return this.layoutManager;
			}
		}

		public SelectionManager SelectionManager
		{
			get
			{
				return this.selectionManager;
			}
		}

		public bool IsDrawingPolyline
		{
			get
			{
				return this.isDrawingPolyline;
			}
			set
			{
				this.isDrawingPolyline = value;
			}
		}

		public bool IsHoldingNode
		{
			get
			{
				return this.isHoldingNode;
			}
			set
			{
				this.isHoldingNode = value;
			}
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Class constructor.
		/// </summary>
		public Editor()
		{
			InitializeComponent();

			this.Cursor = Cursors.None;
			this.undoManager = new UndoManager();
			this.layoutManager = new LayoutManager();
			this.logicManager = new LogicManager();
			this.selectionManager = new SelectionManager(this);
			//this.selectionManager.Pick += new EventHandler<NodeEventArgs>(selectionManager_Pick);
			//this.selectionManager.Drop += new EventHandler<NodeEventArgs>(selectionManager_Drop);
			this.LayerWorking.VisualObjectDrawn += new EventHandler<LayerEventArgs>(layerCircuit_VisualObjectDrawn);

			//this.viewTool = new ViewTool();
			//this.viewTool.Editor = this;

			Helper.RegisterMouseWheel();
			Helper.MouseWheelChanged += new EventHandler<EventArgs<int>>(Helper_MouseWheelChanged);

			Reload();
		}
		#endregion

		#region Methods
		/// <summary>
		/// Clear all layers and remove undo history.
		/// </summary>
		public void Clear(bool removeUndoHistory)
		{
			this.LayerWorking.Clear();
			this.LayerPreview.Clear();
			this.LayerSelect.Clear();
			this.LayerFloat.Clear();
			this.LayerNodes.Clear();
			this.LayerCursor.Clear();

			if (removeUndoHistory)
			{
				this.undoManager.Clear();
			}
		}

		/// <summary>
		/// Reload and clear all layers, redraw grid and remove undo history.
		/// </summary>
		public void Reload()
		{
			layerContainer.DataContext = null;
			layerContainer.DataContext = this.Palette;

			layerCursor.Width = this.Width;
			layerCursor.Height = this.Height;
			layerCursor.Clear();

			layerSelect.Width = this.Width;
			layerSelect.Height = this.Height;
			layerSelect.Clear();

			layerNodes.Width = this.Width;
			layerNodes.Height = this.Height;
			layerNodes.Clear();

			layerFloat.Width = this.Width;
			layerFloat.Height = this.Height;
			layerFloat.Clear();

			layerPreview.Width = this.Width;
			layerPreview.Height = this.Height;
			layerPreview.Clear();

			layerHighlight.Width = this.Width;
			layerHighlight.Height = this.Height;
			layerHighlight.Clear();

			layerWorking.Width = this.Width;
			layerWorking.Height = this.Height;
			layerWorking.Clear();

			layerGrid.Width = this.Width;
			layerGrid.Height = this.Height;
			layerGrid.Clear();
			layerGrid.DrawGrid();

			this.undoManager.Clear();
		}

		/// <summary>
		/// Trigger action according to the pressed key.
		/// </summary>
		/// <param name="key"></param>
		public new bool KeyDown(object sender, KeyEventArgs e)
		{
			bool handled = false;

			if (this.SelectedTool is IRawTool)
			{
				handled = ((IRawTool)this.SelectedTool).KeyDown(sender, e);
			}
			else if (this.SelectedTool is IEditorTool)
			{
				handled = ((IEditorTool)this.SelectedTool).KeyDown(e.Key);
			}

			if (!handled)
			{
				switch (e.Key)
				{
					case Key.Escape:
						ReleaseTool();
						break;

					//Test
					case Key.T:
						LogicManager.FindMatrix(this.LayerWorking);
						//bool detected = LayoutManager.CheckCollision(
						//    layerCircuit.Children[0] as FrameworkElement,
						//    layerCircuit.Children[1] as FrameworkElement);
						//List<FrameworkElement> elements = LogicTree.FindObjects<NodeTag>(layerCircuit as Panel);
						//LogicTree.FindOverlappedElements(layerCircuit as Panel, layerCircuit.Children[0] as FrameworkElement, ref elements);
						//Console.WriteLine(elements.Count);
						break;

					//Toggle fullscreen
					case Key.F:
						Application.Current.Host.Content.IsFullScreen ^= true;
						break;

					case Key.Q:
						//case Key.Escape: //Reserved for fullscreen exit
						this.isDrawingPolyline = false;
						break;

					case Key.C:
						if (this.isDrawingPolyline)
						{
							//Stop drawing polyline on tool change
							this.isDrawingPolyline = false;
							//this.SelectionManager.Select(this.LayerWorking.LastPolyline);
						}
						break;

					case Key.X:
						//if (this.holdingNode != null)
						//{
						//    this.holdingNode.Clear();
						//    layerCircuit.LastPolyline.Data = layerCircuit.LastPolyline.Data; //Update
						//}
						break;

					//Merge selected nodes
					case Key.M:
						break;

					case Key.S:
						this.SelectionManager.UnselectAll();
						break;

					case Key.A:
						this.SelectionManager.SelectAll();
						break;

					case Key.V:
						this.SelectedTool = new HandTool();
						break;

					case Key.D:
					case Key.Delete:
						Delete();
						break;
				}
			}
			return handled;
		}

		/// <summary>
		/// Trigger action according to the released key.
		/// </summary>
		/// <param name="key"></param>
		public new bool KeyUp(object sender, KeyEventArgs e)
		{
			bool handled = false;

			if (this.SelectedTool is IRawTool)
			{
				handled = ((IRawTool)this.SelectedTool).KeyUp(sender, e);
			}
			else if (this.SelectedTool is IEditorTool)
			{
				handled = ((IEditorTool)this.SelectedTool).KeyUp(e.Key);
			}

			if (!handled)
			{
				switch (e.Key)
				{
					default:
						break;
				}
			}
			return handled;
		}

		/// <summary>
		/// Drop the selected tool. Tool is still selected but not active.
		/// </summary>
		public void ReleaseTool()
		{
			this.layerFloat.Clear();
			this.LayerPreview.Clear();
			this.isMousePressed = false;
			this.isHoldingNode = false;

			if (this.SelectedTool != null)
			{
				if (this.isDrawingPolyline)
				{
					//Stop drawing polyline on tool change
					this.isDrawingPolyline = false;
					//this.SelectionManager.Select(this.LayerWorking.LastPolyline);
				}
			}
		}

		public void UpdateSize()
		{
			//TODO: Implement clipping area
		}

		/// <summary>
		/// Find an element by name.
		/// </summary>
		/// <param name="elementName">Name of an element.</param>
		/// <returns>Returns found element or null if element cannot be found.</returns>
		public FrameworkElement Find(string elementName)
		{
			FrameworkElement element = null;
			if (!string.IsNullOrEmpty(elementName))
			{
				object obj = this.LayerWorking.FindName(elementName);
				if (obj != null && obj is FrameworkElement)
				{
					element = obj as FrameworkElement;
				}
			}
			return element;
		}

		//Can user perform action? CanUndo, CanRedo, CanSelect... actions in a dictionary.
		//public Dictionary<string, bool> GetAllowedActions()
		//{
		//    Dictionary<string, bool> result = new Dictionary<string, bool>();
		//    result.Add("UnselectAll", this.SelectedElements.Count > 0);
		//    //result.Add("Undo", this.undoManager.CanUndo);
		//    //result.Add("Redo", this.undoManager.CanRedo);
		//    //result.Add("Delete", null);
		//    //result.Add("DeleteAll", null);
		//    //result.Add("MoveToBack", null);
		//    //result.Add("MoveToFront", null);
		//    //result.Add("SelectAll", null);
		//    //result.Add("UnselectAll", null);
		//    //result.Add("SetProperties", null);
		//    return result;
		//}
		#endregion

		#region Bubble events
		public event RoutedEventHandler NodeInserted;
		protected virtual void OnNodeInserted(RoutedEventArgs e)
		{
			if (NodeInserted != null)
			{
				NodeInserted(this, e);
			}
		}
		#endregion

		#region Import/Export
		/// <summary>
		/// Overloaded. Import visual objects to the editor. Previous objects on the LayerWorking are preserved.
		/// </summary>
		/// <param name="xaml">Xaml string.</param>
		/// <returns>Returns number of imported elements.</returns>
		public int Import(string xaml)
		{
			return Import(xaml, true);
		}

		/// <summary>
		/// Import visual objects to the editor. Previous objects on the LayerWorking are preserved.
		/// </summary>
		/// <param name="xaml">Xaml string.</param>
		/// <param name="group">True to group imported objects.</param>
		/// <returns>Returns number of imported elements.</returns>
		public int Import(string xaml, bool group)
		{
			int count = 0;
			object obj = XamlReader.Load(xaml);
			if (obj != null && obj is FrameworkElement)
			{
				List<FrameworkElement> list = new List<FrameworkElement>();
				FrameworkElement element = obj as FrameworkElement;
				if (element is Panel)
				{
					#region Container
					Panel panel = element as Panel;
					foreach (FrameworkElement item in panel.Children)
					{
						this.LayerWorking.Children.Add(item);
						list.Add(item);
					}
					#endregion
				}
				else
				{
					#region Element
					this.LayerWorking.Children.Add(element);
					list.Add(element);
					#endregion
				}
				count = list.Count;
				if (group && count > 0)
				{
					this.SelectionManager.Group(list);
				}
			}
			return count;
		}

		/// <summary>
		/// Export visual objects to the xaml string.
		/// </summary>
		/// <param name="xaml">[out] Returned xaml string.</param>
		/// <returns>Returns number of exported elements (top layer).</returns>
		public int Export(out string xaml)
		{
			xaml = null;
			int count = 0;

			throw new NotImplementedException();

#if SILVERLIGHT
			//xaml = Converter.ObjectToString(layerCircuit);
			//TODO: Serialize with Silverlight toolkit
#else
      //TODO: Serialize with the serialization library
#endif

			return count;
		}
		#endregion
	}
}
